package com.hongfeng.web.dao.common.impl;

import java.io.Serializable;
import java.sql.Timestamp;
import java.util.Date;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.transaction.annotation.Transactional;

import com.hongfeng.web.commons.Page;
import com.hongfeng.web.dao.common.GenericDao;
import com.hongfeng.web.item.QueryItem;
import com.hongfeng.web.item.exp.FieldExpression;
import com.hongfeng.web.item.tag.FieldOperator;

@Transactional
public class GenericDaoJpa<T, PK extends Serializable> implements
		GenericDao<T, PK> {

	protected Log log = LogFactory.getLog(GenericDaoJpa.class);

	@PersistenceContext(unitName = "mysql")
	private EntityManager entityManager;

	private Class<T> persistentClass;

	public GenericDaoJpa(final Class<T> persistentClass) {
		this.persistentClass = persistentClass;
	}

	public void create(T entity) {
		entityManager.persist(entity);
	}

	public T read(PK id) {
		return entityManager.find(this.persistentClass, id);
	}

	public void delete(T entity) {
		entityManager.remove(entityManager.merge(entity));
	}

	public void update(T entity) {
		entityManager.merge(entity);
	}

	public List<T> findAll() {
		return this.findAll(new QueryItem(persistentClass));
	}

	public List<T> findAll(QueryItem item) {
		return find(item);
	}

	public Page<T> findPage(Page<T> page) {
		return this.findPage(new QueryItem(persistentClass), page);
	}

	@SuppressWarnings("unchecked")
	public Page<T> findPage(QueryItem item, Page<T> page) {
		String hql = item.generateHql();
		String countHql = "select count(*) "
				+ removeSelect(removeOrders(removeFetch(hql)));
		Query countQuery = createQuery(item, countHql);
		List<T> countlist = countQuery.getResultList();
		Long totalCount = 0L;
		if (countlist.size() > 0) {
			totalCount = (Long) countlist.get(0);
		}

		if (totalCount < 1) {
			return new Page<T>();
		}

		Query qry = createQuery(item, hql);
		qry.setFirstResult(page.getStartRow());
		qry.setMaxResults(page.getPageSize());

		page.setData(qry.getResultList());
		page.setTotal(totalCount.intValue());
		return page;
	}

	@SuppressWarnings("unchecked")
	private List<T> find(QueryItem item) {
		String hql = item.generateHql();
		Query qry = createQuery(item, hql);
		return qry.getResultList();
	}

	private String removeSelect(String hql) {
		int beginPos = hql.toLowerCase().indexOf("from");
		return hql.substring(beginPos);
	}

	private String removeFetch(String hql) {
		String filter = "fetch";
		int beginPos = hql.toLowerCase().indexOf(filter);
		while (beginPos > -1) {
			hql = hql.substring(0, beginPos)
					+ hql.substring(beginPos + filter.length());
			beginPos = hql.toLowerCase().indexOf(filter);
		}

		return hql;
	}

	private String removeOrders(String hql) {
		Pattern p = Pattern.compile("order\\s*by[\\w|\\W|\\s|\\S]*",
				Pattern.CASE_INSENSITIVE);
		Matcher m = p.matcher(hql);
		StringBuffer sb = new StringBuffer();
		while (m.find()) {
			m.appendReplacement(sb, "");
		}
		m.appendTail(sb);

		return sb.toString();
	}

	private Query createQuery(QueryItem item, String hql) {
		List<Object> values = item.generateHqlValues();
		Query qry = entityManager.createQuery(hql);
		qry.setHint("org.hibernate.cacheable", true);
		if (values == null) {
			return qry;
		}
		List<FieldExpression> fieldExps = item.getFieldExps();
		for (int i = 0; i < values.size(); i++) {
			Object value = values.get(i);
			if (value instanceof Long) {
				qry.setParameter(i + 1, ((Long) value).longValue());
			} else if (value instanceof Integer) {
				qry.setParameter(i + 1, ((Integer) value).intValue());
			} else if (value instanceof Boolean) {
				qry.setParameter(i + 1, ((Boolean) value).booleanValue() ? 1
						: 0);
			} else if (value instanceof Date || value instanceof Timestamp) {
				qry.setParameter(i + 1, new Timestamp(((Date) value).getTime()));
			} else {
				FieldOperator fieldOperate = fieldExps.get(i)
						.getFieldOperator();
				if (FieldOperator.LIKE.equals(fieldOperate)) {
					qry.setParameter(
							i + 1,
							"%"
									+ (value.toString().trim().replaceAll("%",
											"\\\\%")) + "%");
				} else {
					qry.setParameter(i + 1, value.toString().trim());
				}
			}
		}
		return qry;
	}

}
